#include "ModelProcessorFactory.h"
#include "../AbstractFactoryMethod.h"

#include <fstream>
#include <filesystem>

#include "../RapidXML/rapidxml.hpp"
#include "../RapidXML/rapidxml_print.hpp"

namespace MMM
{

// Need to add a template specialization to the library, 
// otherwise we will get undefined references when we use the ModelProcessorFactory externally.
template class MMM_IMPORT_EXPORT AbstractFactoryMethod<ModelProcessorFactory, void*>;


rapidxml::xml_node<>* ModelProcessorFactory::getConfigNode(rapidxml::xml_node<>* node)
{
	std::string factoryName;
	while (node)
	{
		std::string nodeName = XML::toLowerCase(node->name());
		if (nodeName == "modelprocessorconfig")
		{
			rapidxml::xml_attribute<> *attr = node->first_attribute("type", 0, false);
			if (!attr)
			{
				MMM_ERROR << "xml Tag: missing attribute 'type'" << endl;
				return nullptr;
			}
			factoryName = attr->value();
			if (factoryName.empty())
			{
				MMM_ERROR << "xml tag: null type string" << endl;
				return nullptr;
			} else
				return node;
		}
		else
		{
			//MMM_INFO << "Ignoring unknown XML tag <" << nodeName << ">" << endl;
		}
		node = node->next_sibling();
	}
	return nullptr;
}

ModelProcessorPtr ModelProcessorFactory::fromConfigFile(const std::string &configFile)
{
	// try to open file
	std::ifstream in(configFile.c_str());

	if (!in.is_open())
	{
		MMM_ERROR << "Could not open XML file:" << configFile << endl;
		return ModelProcessorPtr();
	}

	std::stringstream buffer;
	buffer << in.rdbuf();
	std::string configXML(buffer.str());
	in.close();

	// search config tag
	std::string factoryName;
	rapidxml::xml_node<>* node = nullptr;
	rapidxml::xml_document<char> doc;   // character type defaults to char
	try
	{
		char* y = doc.allocate_string(configXML.c_str());
		doc.parse<0>(y);					// 0 means default parse flags

		node = doc.first_node();
		node = getConfigNode(node);
	}
	catch (rapidxml::parse_error& e)
	{
		MMM_ERROR << "Could not parse data in XML definition" << endl
			<< "Error message:" << e.what() << endl
			<< "Position: " << endl << e.where<char>() << endl;
		return ModelProcessorPtr();
	}
	catch (std::exception& e)
	{
		MMM_ERROR << "Error while parsing XML definition" << endl
			<< "Error code:" << e.what() << endl;
		return ModelProcessorPtr();
	}
	catch (...)
	{
		cout << "Error while parsing XML definition" << endl;
		return ModelProcessorPtr();
	}
	return getModelProcessorFromNode(node);
}

ModelProcessorPtr ModelProcessorFactory::getModelProcessorFromNode(rapidxml::xml_node<>* node)
{
	std::string factoryName;
	if (!node)
	{
		MMM_ERROR << "Could not determine ModelProcessorFactory..." << endl;
		return ModelProcessorPtr();
	}
	rapidxml::xml_attribute<> *attr = node->first_attribute("type", 0, false);
	if (!attr)
	{
		MMM_ERROR << "Could not determine type attribute from ModelProcessorFactory node..." << endl;
		return ModelProcessorPtr();
	}
	factoryName = attr->value();

	ModelProcessorFactoryPtr modelFactory = MMM::ModelProcessorFactory::fromName(factoryName, NULL);
	if (!modelFactory)
	{
		MMM_ERROR << "ModelProcessorFactory with name " << modelFactory << " not known..." << endl;
		return ModelProcessorPtr();
	}

	ModelProcessorPtr modelProcessor = modelFactory->createModelProcessor();
	if (!modelProcessor)
	{
		MMM_ERROR << "ModelProcessorFactory with name " << modelFactory << ": error whil creating processor..." << endl;
		return ModelProcessorPtr();
	}
	std::stringstream xmlNode;
	xmlNode << *node;
	if (!modelProcessor->setupXML(xmlNode.str()))
	{
		cout << "Error while configuring model processor '" << modelFactory << "' from file" << endl;
		return ModelProcessorPtr();
	}
	return modelProcessor;
}

ModelProcessorPtr ModelProcessorFactory::fromMotionFile(const std::string &motionFile, const std::string &motionName)
{
	// try to open file
	std::ifstream in(motionFile.c_str());

	if (!in.is_open())
	{
		MMM_ERROR << "Could not open XML file:" << motionFile << endl;
		return ModelProcessorPtr();
	}

	std::stringstream buffer;
	buffer << in.rdbuf();
	std::string configXML(buffer.str());
	in.close();

	// search config tag

	rapidxml::xml_node<>* node = nullptr;
	rapidxml::xml_document<char> doc;   // character type defaults to char
	try
	{
		char* y = doc.allocate_string(configXML.c_str());
		doc.parse<0>(y);					// 0 means default parse flags

		rapidxml::xml_node<>* mainNode = doc.first_node(XML::MOTION_XML_ROOT.c_str(), 0, false);
		if (mainNode)
			mainNode = mainNode->first_node("motion");
		else
			MMM_ERROR << "Could not find " << XML::MOTION_XML_ROOT << " node" << endl;

		while (mainNode)
		{
			std::string nodeName = XML::toLowerCase(mainNode->name());
			if (nodeName == "motion")
			{
				rapidxml::xml_attribute<> *attr = mainNode->first_attribute("name", 0, false);
				if (motionName.empty() || (attr && attr->value() == motionName))
				{
					node = getConfigNode(mainNode->first_node());
					break;
				}
			}
			mainNode = mainNode->next_sibling();
		}
	}
	catch (rapidxml::parse_error& e)
	{
		MMM_ERROR << "Could not parse data in XML definition" << endl
			<< "Error message:" << e.what() << endl
			<< "Position: " << endl << e.where<char>() << endl;
		return ModelProcessorPtr();
	}
	catch (std::exception& e)
	{
		MMM_ERROR << "Error while parsing XML definition" << endl
			<< "Error code:" << e.what() << endl;
		return ModelProcessorPtr();
	}
	catch (...)
	{
		cout << "Error while parsing XML definition" << endl;
		return ModelProcessorPtr();
	}

	return getModelProcessorFromNode(node);
}

}
